package com.zh.service.impl;

import java.math.BigDecimal;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.partnerpayments.jsapi.model.Transaction;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse;
import com.zh.constants.Constants;
import com.zh.exception.GlobalException;
import com.zh.mapstruct.PayStructMapper;
import com.zh.pojo.bo.PayOrderBo;
import com.zh.pojo.bo.PayResultBo;
import com.zh.pojo.domain.PayOrder;
import com.zh.mapper.PayOrderMapper;
import com.zh.pojo.domain.WxpayFlow;
import com.zh.pojo.vo.LoginVo;
import com.zh.result.JSONResult;
import com.zh.service.IPayOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zh.service.IWxpayFlowService;
import com.zh.template.GetTuiTemplate;
import com.zh.template.PayTemplate;
import com.zh.utils.AssertUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author 初四
 * @since 2024-07-07
 */
@Service
@Slf4j
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements IPayOrderService {
    private static final Date nowDate = new Date(); // 请自行

    @Autowired
    private PayTemplate payTemplate;
    @Autowired
    private GetTuiTemplate getTuiTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private IWxpayFlowService wxpayFlowService;
    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;


    /**
     * 微信程序支付回调
     *
     * @param request Http请求
     */
    @Override
    public void wechatPayNotify(HttpServletRequest request) {

        try {
            String Signature = request.getHeader("Wechatpay-Signature");
            String Serial = request.getHeader("Wechatpay-Serial");
            String Nonce = request.getHeader("Wechatpay-Nonce");
            String Timestamp = request.getHeader("Wechatpay-Timestamp");
            //拿到body中的数据
            byte[] bytes = new byte[request.getContentLength()];
            IOUtils.readFully(request.getInputStream(), bytes);
            String body = new String(bytes, StandardCharsets.UTF_8);
            //  1. 处理支付结果,验证签名,解压数据, 拿到支付结果
            Transaction transaction = payTemplate.parserNotify(Serial, Nonce, Signature, Timestamp, body);
            log.info("支付结果数据transaction: {}", transaction);

            // 注意幂等处理 + 分布式锁 在查询数据库之前上锁
            // 2、修改支付单 : 修改状态为已支付, 并修改支付时间

            // 获取支付单号
            String outTradeNo = transaction.getOutTradeNo();
            String lockName = String.format(Constants.Lock.PAY_ORDER_KEY, outTradeNo);
            RLock lock = redissonClient.getLock(lockName);
            try {
                lock.lock();
                log.info("加锁成功 {}", lock);
                PayOrder payOrder = super.getOne(new LambdaQueryWrapper<PayOrder>().eq(PayOrder::getPayOrderNo, outTradeNo));
                AssertUtil.isNotNull(payOrder, "未找到支付单");
                //幂等处理
                if(payOrder.getPayStatus().equals(Constants.pay.PAY_STATUS_SUCCESS))return ;


                if (transaction.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS)) {
                    // 修改支付单状态 为已支付
                    payOrder.setPayStatus(Constants.pay.PAY_STATUS_SUCCESS);
                    payOrder.setUpdateTime(nowDate);
                    payOrder.setSuccessPayTime(nowDate);
                    // 1、2 保存支付流水
                    WxpayFlow wxpayFlow = new WxpayFlow();
                    wxpayFlow.setAmount(payOrder.getAmount());
                    wxpayFlow.setSpAppid(transaction.getSpAppid());
                    wxpayFlow.setSpMchid(transaction.getSpMchid());
                    wxpayFlow.setBankType(transaction.getBankType());
                    wxpayFlow.setOutTradeNo(transaction.getOutTradeNo());
                    wxpayFlow.setPayerOpenid(transaction.getPayer().getSpOpenid());
                    wxpayFlow.setUserId(payOrder.getPayUserId());
                    wxpayFlow.setSuccessTime(nowDate);
                    wxpayFlow.setTradeState(transaction.getTradeState().name());
                    wxpayFlow.setTradeStateDesc(transaction.getTradeStateDesc());
                    wxpayFlow.setTradeType(transaction.getTradeType().name());
                    wxpayFlow.setAttach(transaction.getAttach());
                    // 如果支付成功 则 保存支付流水，
//                    wxpayFlowService.save(wxpayFlow);
                    // 3、向MQ发送事务消息，修改订单状态 ， 因为涉及到两个数据库的写操作， 需要使用事务消息
                    sendMQTransaction(transaction, payOrder, wxpayFlow);
                }
            } finally {
                if (lock.isLocked()) {
                    // 释放锁
                    lock.unlock();
                    log.info("释放锁成功 {}", lock);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new GlobalException("处理失败");
        }

    }

    /**
     * 向MQ发送事务消息
     * @param transaction
     * @param payOrder
     * @param wxpayFlow
     */
    private void sendMQTransaction(Transaction transaction, PayOrder payOrder, WxpayFlow wxpayFlow) {
        // 3.1 构建扩展参数
        HashMap<String, Object> args = new HashMap<>();
        args.put("payOrder", payOrder);
        args.put("wxpayFlow", wxpayFlow);
        // 3.2、构建消息 message
        PayResultBo payResultBo = new PayResultBo().setOrderNo(payOrder.getOrderNo())
                .setSuccess(transaction.getTradeState().equals(Transaction.TradeStateEnum.SUCCESS));
        Message<PayResultBo> message = MessageBuilder.withPayload(payResultBo).build();
        TransactionSendResult sendResult = rocketMQTemplate.sendMessageInTransaction(  //事务监听器
                Constants.MQ.TX_GROUP_PAY_ORDER_RESULT, // 监听器 事务组
                Constants.MQ.TOPIC_PAY_RESULT + ":" + Constants.MQ.TAGS_PAY_RESULT,
                message,  // 消息对象
                args   //扩展参数
        );
        SendStatus sendStatus = sendResult.getSendStatus();
        //如何保证消息不丢失？
        AssertUtil.isTrue(sendStatus == SendStatus.SEND_OK, "支付结果消息发送失败");
        log.info("支付消息推送到MQ成功: " + sendResult);
    }

    /**
     * 修改支付单状态 ， 保存订单流水
     * @param payOrder  支付单对象
     * @param wxpayFlow 订单流水对象
     */
    @Override
    public void updatePayOrderAndSaveFlow(PayOrder payOrder, WxpayFlow wxpayFlow) {
        wxpayFlowService.save(wxpayFlow);
        super.updateById(payOrder);
        log.info("修改支付单，保存流水 {} , {}",payOrder, wxpayFlow);
    }

    /**
     * 微信支付
     *
     * @param orderNo
     * @return
     */
    @Override
    public JSONResult wechatPay(String orderNo) {
        // 1、参数判断
        AssertUtil.isNotNull(orderNo, "orderNo不能为空");
        PayOrder payOrder = super.getOne(new LambdaQueryWrapper<PayOrder>().eq(PayOrder::getOrderNo, orderNo));
        AssertUtil.isNotNull(payOrder, "订单不存在");
        // 2、修改订单支付状态， 支付方式、修改时间
        payOrder.setUpdateTime(nowDate);
        // 支付单状态
        payOrder.setPayStatus(Constants.pay.PAY_STATUS_LOADING);
        // 支付方式 （微信支付）
        payOrder.setPayType(Constants.payType.PAY_TYPE_WX);
        // 获取支付单号
        String payOrderNo = payOrder.getPayOrderNo();
        //处理二次支付申请的情况 如果 支付状态为 支付中则关闭订单 , 这样可以重复发起支付申请
        if (payOrder.getPayStatus().equals(Constants.pay.PAY_STATUS_LOADING)) {
            // 关闭订单
            payTemplate.closeOrder(payOrderNo);
            //重新 设置新的 支付单号
            String newPayOrderNo = IdUtil.createSnowflake(1, 1).nextIdStr();
            payOrder.setPayOrderNo(newPayOrderNo);
        }
        super.updateById(payOrder);


        // 获取openId 从redis中取
        String infoKey = String.format(Constants.Redis.LOGIN_INFO_KEY, StpUtil.getLoginIdAsLong());
        LoginVo loginInfo = (LoginVo) redisTemplate.opsForValue().get(infoKey);
        // 3、发起(创建)支付申请
        PrepayWithRequestPaymentResponse wechatPayResponse = payTemplate.createWechatPay(loginInfo.getOpenId(),
                payOrder.getSubject(), payOrder.getAmount(), payOrderNo);

        return JSONResult.success(wechatPayResponse);
    }


    /**
     * 创建支付单 ， 并对支付单做幂等处理， 防止消费者 重复消费 创建多个支付单
     *
     * @param payOrderBo 支付单数据
     */
    @Override
    public void create(PayOrderBo payOrderBo) {
        // 做幂等等处理，防止重复创建
        PayOrder byPayOrderNo = getByPayOrderNo(payOrderBo.getOrderNo());
        if (byPayOrderNo != null) {
            return;
        }
        PayOrder payOrder = PayStructMapper.INSTANCE.payOrderBoConvertPayOrder(payOrderBo);
        payOrder.setPayStatus(Constants.pay.PAY_STATUS_WAIT);
        payOrder.setCreateTime(new Date());
        payOrder.setPayOrderNo(IdUtil.createSnowflake(1, 1).nextIdStr());
        super.save(payOrder);
        // 构建消息
        HashMap<String, Object> param = new HashMap<>();
        param.put("message", "您有一个未支付的订单，请尽快支付。");
        param.put("orderNo", payOrder.getOrderNo());
        param.put("messageType", Constants.pay.MESSAGE_TYPE_PAY);
//        String message = param.toString();
        String message = JSON.toJSONString(param);
        // 构建推送参数向小程序推送消息
        getTuiTemplate.pushToSingleByAlias(payOrder.getPayUserId().toString(), message);
    }


    /**
     * 查询支付单状态
     *
     * @param orderNo 订单号
     * @return
     */
    public PayOrder getByPayOrderNo(String orderNo) {
        return super.getOne(new LambdaQueryWrapper<PayOrder>().eq(PayOrder::getOrderNo, orderNo));
    }
}
